#!/bin/bash
# =============================================================================
#  SPZGate 2.0.4 — установщик / installer
#  Запуск: sudo bash install.sh [--dir /opt/spzgate] [--port-smtp 25] [--port-http 80] [--port-https 443] [--port-mysql 13306] [--lang ru|en|kk] [--serverid 001] [--no-build] [--offline]
# =============================================================================
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive

# ── Цвета ─────────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'

# ── Язык / language ────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LANG_CODE="en"
LANG_EXPLICIT=0
load_lang() {
    local f="$SCRIPT_DIR/lang/install-${LANG_CODE}.sh"
    [[ -f "$f" ]] || { LANG_CODE="en"; f="$SCRIPT_DIR/lang/install-en.sh"; }
    source "$f"
}
load_lang

info()   { echo -e "${CYAN}[INFO]${NC}  $*"; }
ok()     { echo -e "${GREEN}[OK]${NC}    $*"; }
warn()   { echo -e "${YELLOW}[WARN]${NC}  $*"; }
error()  { echo -e "${RED}[ERROR]${NC} $*" >&2; }
die()    { error "$*"; exit 1; }
header() {
    echo -e "\n${BOLD}══════════════════════════════════════════════${NC}"
    echo -e "${BOLD}  $*${NC}"
    echo -e "${BOLD}══════════════════════════════════════════════${NC}"
}

# ── Интерактивный запрос (с fallback при отсутствии TTY) ─────────────────────
ask() {
    local msg="$1" default="${2:-n}"
    local prompt answer
    [[ "$default" == "y" ]] && prompt="[Y/n]" || prompt="[y/N]"
    # Если нет TTY — используем default без вопроса
    if ! [ -t 0 ] && ! { true >/dev/tty; } 2>/dev/null; then
        warn "$(printf "$MSG_NO_TTY" "$default" "$msg")"
        [[ "$default" =~ ^[Yy] ]]
        return
    fi
    echo -en "${YELLOW}  ? ${msg}${NC} $prompt " >/dev/tty
    read -r answer </dev/tty 2>/dev/null || answer="$default"
    answer="${answer:-$default}"
    [[ "$answer" =~ ^[Yy] ]]
}

# ── Параметры ─────────────────────────────────────────────────────────────────
INSTALL_DIR="$SCRIPT_DIR"
PORT_SMTP=25
PORT_HTTP=80
PORT_HTTPS=443
PORT_MYSQL=13306
SERVERID="001"
BUILD=1
OFFLINE=0

while [[ $# -gt 0 ]]; do
    case $1 in
        --dir)        INSTALL_DIR="$2"; shift 2 ;;
        --port-smtp)  PORT_SMTP="$2";   shift 2 ;;
        --port-http)  PORT_HTTP="$2";   shift 2 ;;
        --port-https) PORT_HTTPS="$2";  shift 2 ;;
        --port-mysql) PORT_MYSQL="$2";  shift 2 ;;
        --serverid)   SERVERID="$2";    shift 2 ;;
        --no-build)   BUILD=0;          shift ;;
        --offline)    OFFLINE=1; BUILD=0; shift ;;
        --lang)
            case "$2" in
                ru|en|kk) LANG_CODE="$2" ;;
                *)        LANG_CODE="en" ;;
            esac
            LANG_EXPLICIT=1
            load_lang
            shift 2 ;;
        --help|-h)
            echo "$MSG_HELP_USAGE"
            echo ""
            echo "$MSG_HELP_DIR"
            echo "$MSG_HELP_SMTP"
            echo "$MSG_HELP_HTTP"
            echo "$MSG_HELP_HTTPS"
            echo "$MSG_HELP_MYSQL"
            echo "$MSG_HELP_LANG"
            echo "$MSG_HELP_SERVERID"
            echo "$MSG_HELP_NOBUILD"
            echo "$MSG_HELP_OFFLINE"
            exit 0 ;;
        *) die "$(printf "$MSG_UNKNOWN_PARAM" "$1")" ;;
    esac
done

# Если язык не указан явно — спрашиваем интерактивно (при наличии TTY)
if [[ $LANG_EXPLICIT -eq 0 ]] && { [ -t 0 ] || { true >/dev/tty; } 2>/dev/null; }; then
    echo -en "${YELLOW}  ? Installation language / Язык установки / Орнату тілі [en/ru/kk]:${NC} [en] " >/dev/tty
    read -r _lang_answer </dev/tty 2>/dev/null || _lang_answer="en"
    _lang_answer="${_lang_answer:-en}"
    if [[ "$_lang_answer" =~ ^[RrРр] ]]; then
        LANG_CODE="ru"
        load_lang
    elif [[ "$_lang_answer" =~ ^[KkҚқ] ]]; then
        LANG_CODE="kk"
        load_lang
    fi
fi

COMPOSE_FILE="$INSTALL_DIR/docker-compose.yml"
SPZ_VERSION="$(cat "$INSTALL_DIR/VERSION" 2>/dev/null | tr -d '[:space:]')"
[[ -n "$SPZ_VERSION" ]] || SPZ_VERSION="?"

[[ $EUID -eq 0 ]] || die "$MSG_RUN_AS_ROOT"
[[ -f "$COMPOSE_FILE" ]] || die "$(printf "$MSG_COMPOSE_NOT_FOUND" "$INSTALL_DIR")"

header "$MSG_TITLE"
info "$(printf "$MSG_DIR" "$INSTALL_DIR")"
info "$(printf "$MSG_PORTS_SUMMARY" "$PORT_SMTP" "$PORT_HTTP" "$PORT_HTTPS" "$PORT_MYSQL")"
info "Server ID: ${SERVERID}"

# ── Whitelabel: custom/ рядом со скриптом → в корень целевой установки ─────────
# Устанавливается через --dir в директорию, отличную от той, где лежит сам
# install.sh (например, релиз с уже готовым брендированным custom/, склонированный
# в сторону) — переносим custom/ ДО docker compose build/up. Критично именно до
# него: иначе Docker сам создаст пустые файлы/каталоги на месте volume-mount'ов
# logo.png/favicon.ico/custom/lang (docker-compose.yml), и подмена не сработает.
if [[ -d "$SCRIPT_DIR/custom" && "$SCRIPT_DIR" != "$INSTALL_DIR" ]]; then
    if [[ -d "$INSTALL_DIR/custom" && -n "$(ls -A "$INSTALL_DIR/custom" 2>/dev/null)" ]]; then
        warn "$(printf "$MSG_CUSTOM_OVERWRITE" "$INSTALL_DIR/custom")"
    fi
    mkdir -p "$INSTALL_DIR/custom"
    cp -a "$SCRIPT_DIR/custom/." "$INSTALL_DIR/custom/"
    ok "$(printf "$MSG_CUSTOM_COPIED" "$INSTALL_DIR/custom")"
fi

# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_OS"
# ══════════════════════════════════════════════════════════════════════════════

OS_ID="unknown"; OS_VER="0"; OS_CODENAME=""; PRETTY_NAME="Unknown"
[[ -f /etc/os-release ]] && . /etc/os-release && \
    OS_ID="${ID:-unknown}" OS_VER="${VERSION_ID:-0}" \
    OS_CODENAME="${VERSION_CODENAME:-}" PRETTY_NAME="${PRETTY_NAME:-Unknown}"

ARCH=$(uname -m)
echo ""
echo -e "${MSG_OS_LABEL}${BOLD}$PRETTY_NAME${NC}"
echo -e "${MSG_CODENAME_LABEL}${BOLD}${OS_CODENAME:-$MSG_NA}${NC}"
echo -e "${MSG_ARCH_LABEL}${BOLD}$ARCH${NC}"
echo -e "${MSG_KERNEL_LABEL}${BOLD}$(uname -r)${NC}"
echo ""

# Матрица совместимости
OS_STATUS="unknown"   # ok | warn | unsupported | unknown
OS_NOTE=""

case "$OS_ID" in
    ubuntu)
        case "$OS_VER" in
            22.04) OS_STATUS="ok";   OS_NOTE="$MSG_OS_UBUNTU_2204" ;;
            24.04) OS_STATUS="ok";   OS_NOTE="$MSG_OS_UBUNTU_2404" ;;
            26.04) OS_STATUS="ok";   OS_NOTE="$MSG_OS_UBUNTU_2604" ;;
            20.04) OS_STATUS="warn"; OS_NOTE="$MSG_OS_UBUNTU_2004" ;;
            24.10|25.04|25.10|23.10|23.04) OS_STATUS="warn"; OS_NOTE="$MSG_OS_UBUNTU_NONLTS" ;;
            18.04|16.04) OS_STATUS="unsupported"; OS_NOTE="$MSG_OS_UBUNTU_TOOOLD" ;;
            *) OS_STATUS="warn"; OS_NOTE="$MSG_OS_UBUNTU_UNKNOWN" ;;
        esac ;;
    debian)
        case "$OS_VER" in
            12) OS_STATUS="ok";   OS_NOTE="$MSG_OS_DEBIAN_12" ;;
            11) OS_STATUS="ok";   OS_NOTE="$MSG_OS_DEBIAN_11" ;;
            10) OS_STATUS="warn"; OS_NOTE="$MSG_OS_DEBIAN_10" ;;
            *)  OS_STATUS="warn"; OS_NOTE="$MSG_OS_DEBIAN_UNKNOWN" ;;
        esac ;;
    linuxmint) OS_STATUS="warn"; OS_NOTE="$MSG_OS_MINT" ;;
    centos|rhel|fedora|almalinux|rocky)
        OS_STATUS="unsupported"
        OS_NOTE="$MSG_OS_REDHAT" ;;
    *) OS_STATUS="unknown"; OS_NOTE="$MSG_OS_UNKNOWN_OS" ;;
esac

echo -e "$(printf "$MSG_COMPAT_LABEL" "$OS_NOTE")"
echo ""

case "$OS_STATUS" in
    ok)
        ok "$MSG_OS_OK" ;;
    warn)
        warn "$MSG_OS_WARN_NOT_RECOMMENDED"
        echo ""
        echo -e "$MSG_RECOMMENDED_VERSIONS"
        echo -e "    ${GREEN}• $MSG_REC_UBUNTU${NC}"
        echo -e "    ${GREEN}• $MSG_REC_DEBIAN${NC}"
        echo ""
        if ! ask "$(printf "$MSG_CONTINUE_ON_OS" "$PRETTY_NAME")" y; then
            die "$MSG_INSTALL_CANCELLED_OS"
        fi ;;
    unsupported)
        error "$(printf "$MSG_OS_UNSUPPORTED" "$PRETTY_NAME")"
        echo ""
        echo -e "  $OS_NOTE"
        echo ""
        echo -e "$MSG_INSTALL_UBUNTU"
        echo -e "    curl -fsSL https://spamorez.ru/spzgate/install.sh | sudo bash"
        exit 1 ;;
    unknown)
        warn "$MSG_OS_UNKNOWN"
        if ! ask "$MSG_CONTINUE_UNKNOWN_OS"; then
            die "$MSG_INSTALL_CANCELLED"
        fi ;;
esac

[[ "$ARCH" == "x86_64" || "$ARCH" == "aarch64" ]] || {
    warn "$(printf "$MSG_NONSTANDARD_ARCH" "$ARCH")"
    ask "$MSG_DOCKER_MAY_NOT_SUPPORT" || die "$MSG_INSTALL_CANCELLED"
}

# apt-диагностика/обновление и системные утилиты полностью пропускаются в
# --offline (нет сети — apt-get update всё равно ничего не найдёт); ожидается,
# что base-утилиты (curl, openssl, perl, ...) уже есть в образе/на хосте.
if [[ "$OFFLINE" -eq 0 ]]; then
# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_APT"
# ══════════════════════════════════════════════════════════════════════════════

# Проверяем sources.list на проблемы
APT_SOURCES_FILE="/etc/apt/sources.list"
APT_PROBLEMS=()

# Ищем cdrom-источники (часто остаются после установки с диска)
if grep -qE "^deb\s+(\[.*\]\s+)?file:///cdrom|^deb\s+cdrom:" "$APT_SOURCES_FILE" 2>/dev/null; then
    APT_PROBLEMS+=("cdrom")
fi

# Проверяем есть ли вообще сетевые репозитории.
# Учитываем три места:
#   1. /etc/apt/sources.list             — классический "deb http://..."
#   2. /etc/apt/sources.list.d/*.list    — классический формат в отдельных файлах
#   3. /etc/apt/sources.list.d/*.sources — DEB822-формат (Ubuntu 24.04+): "URIs: http://..."
NET_REPOS=0
if grep -qrE "^deb[[:space:]]+https?://" "$APT_SOURCES_FILE" /etc/apt/sources.list.d/ 2>/dev/null; then
    NET_REPOS=1
fi
if grep -qrE "^URIs:[[:space:]]+https?://" /etc/apt/sources.list.d/ 2>/dev/null; then
    NET_REPOS=1
fi
if [[ "$NET_REPOS" -eq 0 ]]; then
    APT_PROBLEMS+=("no_net_repos")
fi

if [[ ${#APT_PROBLEMS[@]} -gt 0 ]]; then
    echo ""
    warn "$MSG_APT_PROBLEMS"
    echo ""

    for problem in "${APT_PROBLEMS[@]}"; do
        case "$problem" in
            cdrom)
                echo -e "  ${RED}•${NC} $MSG_CDROM_FOUND"
                echo -e "    $MSG_CDROM_ERROR"
                ;;
            no_net_repos)
                echo -e "  ${RED}•${NC} $MSG_NO_NET_REPOS"
                echo -e "    $MSG_APT_UPDATE_WILL_FAIL"
                ;;
        esac
    done

    echo ""
    echo -e "${BOLD}$MSG_SUGGESTED_FIXES${NC}"

    for problem in "${APT_PROBLEMS[@]}"; do
        case "$problem" in
            cdrom)
                echo -e "  ${CYAN}1.${NC} $MSG_FIX_CDROM"
                ;;
            no_net_repos)
                echo -e "  ${CYAN}2.${NC} $(printf "$MSG_FIX_REPOS" "${OS_CODENAME:-jammy}")"
                ;;
        esac
    done

    echo ""
    if ask "$MSG_FIX_APT_AUTO"; then
        # Исправляем cdrom
        if [[ " ${APT_PROBLEMS[*]} " =~ " cdrom " ]]; then
            sed -i \
                -e 's|^deb \[check-date=no\] file:///cdrom|# deb [check-date=no] file:///cdrom|g' \
                -e 's|^deb file:///cdrom|# deb file:///cdrom|g' \
                -e 's|^deb cdrom:|# deb cdrom:|g' \
                "$APT_SOURCES_FILE"
            ok "$MSG_CDROM_COMMENTED"
        fi

        # Добавляем сетевые репозитории если нет
        if [[ " ${APT_PROBLEMS[*]} " =~ " no_net_repos " ]]; then
            CODENAME="${OS_CODENAME:-jammy}"
            cat >> "$APT_SOURCES_FILE" << EOF

$(printf "$MSG_REPO_COMMENT" "$(date '+%Y-%m-%d')")
deb http://archive.ubuntu.com/ubuntu ${CODENAME} main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu ${CODENAME}-updates main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu ${CODENAME}-security main restricted universe multiverse
EOF
            ok "$(printf "$MSG_REPOS_ADDED" "$CODENAME")"
        fi
    else
        die "$MSG_NO_WORKING_REPOS"
    fi
fi

# Обновляем индекс
info "$MSG_APT_UPDATE"
if ! apt-get update -q 2>/tmp/apt-update.log; then
    warn "$MSG_APT_UPDATE_ERRORS"
    grep "^E:" /tmp/apt-update.log | head -5 | sed 's/^/  /'
    echo ""
    if ! ask "$MSG_CONTINUE_DESPITE_APT"; then
        die "$MSG_FIX_APT_MANUALLY"
    fi
else
    ok "$MSG_APT_UPDATED"
fi

# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_UTILS"
# ══════════════════════════════════════════════════════════════════════════════

apt-get install -y -qq \
    ca-certificates \
    curl \
    gnupg \
    lsb-release \
    make \
    openssl \
    net-tools \
    perl \
    libdbi-perl \
    libjson-xs-perl \
    libdbd-mysql-perl \
    > /dev/null
ok "$MSG_UTILS_INSTALLED"
else
    header "$MSG_SECTION_APT"
    info "$MSG_OFFLINE_APT_SKIPPED"
fi

# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_DOCKER"
# ══════════════════════════════════════════════════════════════════════════════

install_docker() {
    info "$MSG_INSTALLING_DOCKER"
    curl -fsSL https://get.docker.com -o /tmp/get-docker.sh
    sh /tmp/get-docker.sh > /tmp/docker-install.log 2>&1 || {
        error "$MSG_DOCKER_INSTALL_FAILED"
        tail -20 /tmp/docker-install.log >&2
        exit 1
    }
    rm -f /tmp/get-docker.sh
    systemctl enable docker --quiet
    systemctl start docker
    ok "$(printf "$MSG_DOCKER_INSTALLED" "$(docker --version)")"
}

if command -v docker &>/dev/null; then
    ok "$(printf "$MSG_DOCKER_ALREADY" "$(docker --version)")"
    docker info &>/dev/null || { info "$MSG_STARTING_DOCKER_DAEMON"; systemctl start docker; }
elif [[ "$OFFLINE" -eq 1 ]]; then
    die "$MSG_OFFLINE_DOCKER_MISSING"
else
    install_docker
fi

# Docker Compose plugin v2
if docker compose version &>/dev/null; then
    ok "$(printf "$MSG_COMPOSE_OK" "$(docker compose version)")"
elif [[ "$OFFLINE" -eq 1 ]]; then
    die "$MSG_OFFLINE_COMPOSE_MISSING"
else
    info "$MSG_INSTALLING_COMPOSE"
    COMPOSE_VER=$(curl -fsSL https://api.github.com/repos/docker/compose/releases/latest \
        | grep '"tag_name"' | sed 's/.*"v\([^"]*\)".*/\1/' 2>/dev/null || echo "2.27.0")
    mkdir -p /usr/local/lib/docker/cli-plugins
    curl -fsSL \
        "https://github.com/docker/compose/releases/download/v${COMPOSE_VER}/docker-compose-linux-$(uname -m)" \
        -o /usr/local/lib/docker/cli-plugins/docker-compose
    chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
    ok "$(printf "$MSG_COMPOSE_INSTALLED" "$(docker compose version)")"
fi

# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_PORTS"
# ══════════════════════════════════════════════════════════════════════════════

check_port() {
    local port=$1 name=$2
    if ss -tlnp 2>/dev/null | grep -q ":${port}[[:space:]]"; then
        local proc
        proc=$(ss -tlnp 2>/dev/null | grep ":${port}[[:space:]]" | grep -oP 'users:\(\("\K[^"]+' || echo "$MSG_UNKNOWN_PROC")
        warn "$(printf "$MSG_PORT_BUSY" "$port" "$name" "$proc")"
        ask "$(printf "$MSG_PORT_CONTINUE" "$port")" || \
            die "$(printf "$MSG_FREE_PORT" "$port")"
    else
        ok "$(printf "$MSG_PORT_FREE" "$port" "$name")"
    fi
}

check_port "$PORT_SMTP"  "SMTP"
check_port "$PORT_HTTP"  "HTTP"
check_port "$PORT_HTTPS" "HTTPS"
check_port "$PORT_MYSQL" "MySQL (external)"

# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_DATA"
# ══════════════════════════════════════════════════════════════════════════════

cd "$INSTALL_DIR"

DATA_DIRS=(
    data/mysql data/credentials data/redis data/certs
    data/qmail/control
    data/qmail/queue
    data/qmail/bounces
    data/qmail/quarantine
    data/qmail/delivered
    logs/mysql logs/php logs/nginx logs/qmail
    build
)
for d in "${DATA_DIRS[@]}"; do mkdir -p "$INSTALL_DIR/$d"; done
chmod 777 "$INSTALL_DIR/data/mysql" 2>/dev/null || true
chmod 700 "$INSTALL_DIR/data/credentials" 2>/dev/null || true
ok "$MSG_DIRS_CREATED"

# ── Server ID ─────────────────────────────────────────────────────────────────
# Единственное место, где генерируется serverid: мастер первичной настройки
# панели его больше не трогает (раньше перегенерировал случайное значение на
# каждом прохождении шага 2, из-за чего уже накопленный карантин/статистика
# отрывались от нового id — quarant_send.pl сравнивает qs_quarantine_id с этим
# файлом и тихо ничего не находит при несовпадении).
mkdir -p "$INSTALL_DIR/data/qmail/control/spamorez"
_SERVERID_FILE="$INSTALL_DIR/data/qmail/control/spamorez/serverid"
echo -n "$SERVERID" > "$_SERVERID_FILE"
ok "$(printf "$MSG_SERVERID_GENERATED" "$SERVERID")"

# delivered: control files (create only if not already present)
_DL_LOCAL="$INSTALL_DIR/data/qmail/control/spamorez/delivered_local"
_DL_LIFE="$INSTALL_DIR/data/qmail/control/spamorez/delivered_lifetime"
[[ -f "$_DL_LOCAL" ]]  || echo -n "1" > "$_DL_LOCAL"
[[ -f "$_DL_LIFE"  ]]  || echo -n "7" > "$_DL_LIFE"

# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_CERT"
# ══════════════════════════════════════════════════════════════════════════════

if [[ ! -f "$INSTALL_DIR/data/certs/spzgate.crt" ]]; then
    info "$MSG_GENERATING_CERT"
    openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
        -keyout "$INSTALL_DIR/data/certs/spzgate.key" \
        -out    "$INSTALL_DIR/data/certs/spzgate.crt" \
        -subj   "/C=RU/O=Spamorez/CN=spzgate" \
        2>/dev/null
    ok "$MSG_CERT_CREATED"
else
    ok "$MSG_CERT_EXISTS"
fi

# qmail TLS: servercert.pem = key + cert in one file
_SERVERCERT="$INSTALL_DIR/data/qmail/control/servercert.pem"
if [[ ! -s "$_SERVERCERT" ]]; then
    openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
        -keyout /tmp/spzqmail-key.pem \
        -out    /tmp/spzqmail-cert.pem \
        -subj   "/C=RU/O=Spamorez/CN=spzgate-qmail" \
        2>/dev/null
    cat /tmp/spzqmail-key.pem /tmp/spzqmail-cert.pem > "$_SERVERCERT"
    chmod 644 "$_SERVERCERT"
    rm -f /tmp/spzqmail-key.pem /tmp/spzqmail-cert.pem
    ok "$MSG_SERVERCERT_CREATED"
else
    ok "$MSG_SERVERCERT_EXISTS"
fi

# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_ENV"
# ══════════════════════════════════════════════════════════════════════════════

# .env с hostname и портами — используется в docker-compose.yml
_HOSTNAME="${SPZ_HOSTNAME:-$(hostname -f 2>/dev/null || hostname)}"
cat > "$INSTALL_DIR/.env" << ENVEOF
HOSTNAME=${_HOSTNAME}
PORT_SMTP=${PORT_SMTP}
PORT_HTTP=${PORT_HTTP}
PORT_HTTPS=${PORT_HTTPS}
PORT_MYSQL=${PORT_MYSQL}
SERVERID=${SERVERID}
ENVEOF
ok "$(printf "$MSG_ENV_SUMMARY" "$_HOSTNAME" "$PORT_SMTP" "$PORT_HTTP" "$PORT_HTTPS" "$PORT_MYSQL")"

# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_WEB"
# ══════════════════════════════════════════════════════════════════════════════

if [[ -f "$INSTALL_DIR/build/web.tar.gz" ]]; then
    ok "$(printf "$MSG_WEB_READY" "$(du -sh "$INSTALL_DIR/build/web.tar.gz" | cut -f1)")"
else
    die "$MSG_WEB_MISSING"
fi

if [[ -f "$INSTALL_DIR/build/control.tar.gz" ]]; then
    ok "$(printf "$MSG_CONTROL_READY" "$(du -sh "$INSTALL_DIR/build/control.tar.gz" | cut -f1)")"
else
    die "$MSG_CONTROL_MISSING"
fi

# ══════════════════════════════════════════════════════════════════════════════
if [[ "$OFFLINE" -eq 1 ]]; then
header "$MSG_SECTION_OFFLINE_IMAGES"
# ══════════════════════════════════════════════════════════════════════════════

IMAGES_BUNDLE="$INSTALL_DIR/images/spzgate-${SPZ_VERSION}-images.tar.gz"
[[ -f "$IMAGES_BUNDLE" ]] || die "$(printf "$MSG_OFFLINE_IMAGES_MISSING" "$IMAGES_BUNDLE")"
info "$(printf "$MSG_OFFLINE_IMAGES_LOADING" "$(du -sh "$IMAGES_BUNDLE" | cut -f1)")"
LOAD_LOG=/tmp/spzgate-images-load.log
if docker load -i "$IMAGES_BUNDLE" > "$LOAD_LOG" 2>&1; then
    ok "$(printf "$MSG_OFFLINE_IMAGES_LOADED" "$(grep -c '^Loaded image' "$LOAD_LOG" || echo 0)")"
else
    error "$MSG_OFFLINE_IMAGES_LOAD_FAILED"
    tail -10 "$LOAD_LOG" >&2
    die "$(printf "$MSG_BUILD_FIX" "$LOAD_LOG")"
fi
fi

# ══════════════════════════════════════════════════════════════════════════════
if [[ $BUILD -eq 1 ]]; then
header "$MSG_SECTION_BUILD"
# ══════════════════════════════════════════════════════════════════════════════

cd "$INSTALL_DIR"
info "$MSG_BUILDING"
BUILD_LOG=/tmp/spzgate-build.log
if docker compose -f docker-compose.yml build 2>&1 | tee "$BUILD_LOG" | \
    grep -E '^(#[0-9]+ |ERROR| => ERROR)' | sed 's/^/  /'; then
    :
fi
# Проверяем реальный статус сборки
if grep -qE '^(ERROR|CANCELED)' "$BUILD_LOG" 2>/dev/null; then
    error "$MSG_BUILD_FAILED"
    grep -E '^(ERROR|CANCELED)' "$BUILD_LOG" | head -10 | sed 's/^/  /'
    die "$(printf "$MSG_BUILD_FIX" "$BUILD_LOG")"
fi
ok "$MSG_IMAGES_BUILT"
else
    ok "$MSG_BUILD_SKIPPED"
fi

# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_SERVICES"
# ══════════════════════════════════════════════════════════════════════════════

# Шаг 1: запускаем только инфраструктуру (mysql, redis, memcached)
# — без зависимых контейнеров, чтобы mysql инициализировался без таймаута
info "$MSG_STARTING_BASE"
docker compose -f docker-compose.yml up -d mysql redis memcached \
    || die "$MSG_BASE_FAILED"
ok "$MSG_BASE_STARTED"

# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_MYSQL_WAIT"
# ══════════════════════════════════════════════════════════════════════════════

# MySQL при первой установке выполняет 01-structure.sql (до 30 минут).
# Ждём до 45 минут с прогресс-индикатором.
info "$MSG_WAITING_MYSQL"
_mysql_ready=0
_waited=0
for i in $(seq 1 270); do
    if docker compose -f docker-compose.yml exec -T mysql \
        mysqladmin --defaults-file=/credentials/.my.cnf ping -h 127.0.0.1 \
        &>/dev/null 2>&1; then
        echo ""
        ok "$(printf "$MSG_MYSQL_READY" "$_waited")"
        _mysql_ready=1
        break
    fi
    # Каждые 60 сек показываем статус
    if (( i % 12 == 0 )); then
        _status=$(docker compose -f docker-compose.yml ps --format "{{.Name}}: {{.Status}}" mysql 2>/dev/null || true)
        printf "\n  [%d %s] %s" "$(( _waited / 60 ))" "$MSG_MIN_UNIT" "$_status"
    else
        printf "."
    fi
    sleep 10
    _waited=$(( _waited + 10 ))
done
echo ""
[[ $_mysql_ready -eq 1 ]] || die "$MSG_MYSQL_TIMEOUT"

# Шаг 2: запускаем остальные сервисы (php, nginx, qmail)
info "$MSG_STARTING_REST"
docker compose -f docker-compose.yml up -d \
    || die "$MSG_CONTAINERS_FAILED"
ok "$MSG_CONTAINERS_STARTED"

info "$MSG_WAITING_QMAIL"
for i in $(seq 1 6); do
    docker compose -f docker-compose.yml exec -T qmail \
        supervisorctl status qmail-smtpd 2>/dev/null | grep -q RUNNING && \
        { ok "$MSG_QMAIL_STARTED"; break; }
    [[ $i -eq 6 ]] && warn "$MSG_QMAIL_FAILED"
    sleep 5
done

# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_QMAIL_CONFIG"
# ══════════════════════════════════════════════════════════════════════════════

if docker compose -f docker-compose.yml exec -T qmail \
    /usr/local/bin/perl /var/qmail/bin/tools/getconfig.pl -f \
    >/tmp/getconfig-init.log 2>&1; then
    ok "$MSG_GETCONFIG_OK"
else
    warn "$MSG_GETCONFIG_WARN"
    tail -3 /tmp/getconfig-init.log | sed 's/^/  /'
fi

# ══════════════════════════════════════════════════════════════════════════════
# 14. Язык веб-панели — по умолчанию DEFLANG='ru', для остальных языков установки
#     переключаем панель на тот же код (en/kk/...), если в app/lang/ есть такой файл
# ══════════════════════════════════════════════════════════════════════════════

if [[ "$LANG_CODE" != "ru" ]]; then
    header "$MSG_SECTION_WEBLANG"
    if docker compose -f docker-compose.yml exec -T php \
        sed -i "s/define('DEFLANG', 'ru')/define('DEFLANG', '${LANG_CODE}')/" /var/www/html/v4/app/config.php \
        >/dev/null 2>&1; then
        ok "$(printf "$MSG_WEBLANG_SET" "$LANG_CODE")"
    else
        warn "$MSG_WEBLANG_FAILED"
    fi
fi

# ══════════════════════════════════════════════════════════════════════════════
# 15. Self-test: MySQL databases and tables
# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_SELFTEST"

_mysql_exec() {
    docker compose -f docker-compose.yml exec -T mysql \
        mysql --defaults-file=/credentials/.my.cnf -h 127.0.0.1 -sN -e "$1" 2>/dev/null
}

_st_ok_cnt=0; _st_fail_cnt=0
_st_ok()   { ok   "  $*"; _st_ok_cnt=$(( _st_ok_cnt + 1 )); }
_st_nok()  { echo -e "${RED}[FAIL]${NC} $*" >&2; _st_fail_cnt=$(( _st_fail_cnt + 1 )); }

# ── Databases ─────────────────────────────────────────────────────────────────
info "$MSG_ST_CHECKING_DBS"
_existing_dbs=$(_mysql_exec "SHOW DATABASES" 2>/dev/null || true)
for _db in spz spz_bill status \
           status_01 status_02 status_03 status_04 status_05 status_06 \
           status_07 status_08 status_09 status_10 status_11 status_12; do
    if echo "$_existing_dbs" | grep -qx "$_db"; then
        _st_ok "$(printf "$MSG_ST_DB_OK" "$_db")"
    else
        _st_nok "$(printf "$MSG_ST_DB_FAIL" "$_db")"
    fi
done

# ── Key tables in spz ─────────────────────────────────────────────────────────
info "$MSG_ST_CHECKING_TBLS_SPZ"
_spz_tbls=$(_mysql_exec \
    "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='spz'" \
    2>/dev/null || true)
for _tbl in spz_domains spz_users spz_motd_client spz_boxes \
            spz_usertypes spz_sessions spz_tokens spz_servers \
            spz_rules spz_filter_rules spz_blacklists spz_whitelists; do
    if echo "$_spz_tbls" | grep -qx "$_tbl"; then
        _st_ok "$(printf "$MSG_ST_TBL_OK" "spz.$_tbl")"
    else
        _st_nok "$(printf "$MSG_ST_TBL_FAIL" "spz.$_tbl")"
    fi
done

# ── Key tables in spz_bill ────────────────────────────────────────────────────
info "$MSG_ST_CHECKING_TBLS_BILL"
_bill_tbls=$(_mysql_exec \
    "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='spz_bill'" \
    2>/dev/null || true)
for _tbl in bill_acc bill_company bill_mail; do
    if echo "$_bill_tbls" | grep -qx "$_tbl"; then
        _st_ok "$(printf "$MSG_ST_TBL_OK" "spz_bill.$_tbl")"
    else
        _st_nok "$(printf "$MSG_ST_TBL_FAIL" "spz_bill.$_tbl")"
    fi
done

# ── Key tables in status ──────────────────────────────────────────────────────
info "$MSG_ST_CHECKING_TBLS_STATUS"
_status_tbls=$(_mysql_exec \
    "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='status'" \
    2>/dev/null || true)
for _tbl in queues domains errors traps ip_status; do
    if echo "$_status_tbls" | grep -qx "$_tbl"; then
        _st_ok "$(printf "$MSG_ST_TBL_OK" "status.$_tbl")"
    else
        _st_nok "$(printf "$MSG_ST_TBL_FAIL" "status.$_tbl")"
    fi
done

# ── Table count in each status_XX (ожидаем 65 таблиц: 31×2 msg + 3 агрегатные) ──
info "$MSG_ST_CHECKING_TBLS_STATUS_XX"
for _db in status_01 status_02 status_03 status_04 status_05 status_06 \
           status_07 status_08 status_09 status_10 status_11 status_12; do
    _cnt=$(_mysql_exec \
        "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_db'" \
        2>/dev/null | tr -d '[:space:]' || echo 0)
    if [[ "${_cnt:-0}" -ge 65 ]]; then
        _st_ok "$(printf "$MSG_ST_STATUSXX_OK" "$_db" "$_cnt")"
    else
        _st_nok "$(printf "$MSG_ST_STATUSXX_FAIL" "$_db" "${_cnt:-0}")"
    fi
done

# ── Summary ───────────────────────────────────────────────────────────────────
echo ""
if [[ $_st_fail_cnt -eq 0 ]]; then
    ok "$(printf "$MSG_ST_ALL_OK" "$_st_ok_cnt")"
else
    warn "$(printf "$MSG_ST_SOME_FAIL" "$_st_ok_cnt" "$_st_fail_cnt")"
fi

# ══════════════════════════════════════════════════════════════════════════════
# 16. Проверка доступности панели управления и готовности к первичной
#     инициализации (нет смысла молчать про 502 — лучше сказать сразу)
# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_PANEL"

info "$(printf "$MSG_PANEL_WAITING" "$PORT_HTTP")"
_panel_code="000"
_panel_waited=0
for i in $(seq 1 18); do
    _panel_code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 5 \
        "http://127.0.0.1:${PORT_HTTP}/" 2>/dev/null || echo "000")
    if [[ "$_panel_code" =~ ^(200|301|302|303)$ ]]; then
        ok "$(printf "$MSG_PANEL_OK" "$_panel_code")"
        break
    fi
    printf "."
    sleep 5
    _panel_waited=$(( _panel_waited + 5 ))
done
echo ""
if [[ ! "$_panel_code" =~ ^(200|301|302|303)$ ]]; then
    warn "$(printf "$MSG_PANEL_TIMEOUT" "$_panel_waited" "$_panel_code")"
fi

if docker compose -f docker-compose.yml exec -T php \
    test -f /var/qmail/control/gate >/dev/null 2>&1; then
    ok "$MSG_PANEL_GATE_SET"
elif docker compose -f docker-compose.yml ps php 2>/dev/null | grep -q php; then
    ok "$MSG_PANEL_GATE_READY"
else
    warn "$MSG_PANEL_GATE_UNKNOWN"
fi

# ══════════════════════════════════════════════════════════════════════════════
# 17. Проверка SMTP-порта — открытый порт 25 ничего не гарантирует: tcpserver
#     может принимать TCP-соединения и тут же ронять их без единого байта
#     (например, при сломанном log/run-пайпе или отсутствующем бинарнике
#     SMTP-демона), пока сервер так и не пришлёт приветствие "220 ...".
# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_SMTP"

info "$(printf "$MSG_SMTP_WAITING" "$PORT_SMTP")"
_smtp_banner=""
_smtp_waited=0
for i in $(seq 1 12); do
    _smtp_banner=$(timeout 5 bash -c \
        "exec 3<>/dev/tcp/127.0.0.1/${PORT_SMTP} && head -1 <&3" 2>/dev/null || true)
    if [[ "$_smtp_banner" == 220* ]]; then
        ok "$(printf "$MSG_SMTP_OK" "$(echo "$_smtp_banner" | tr -d '\r\n')")"
        break
    fi
    printf "."
    sleep 5
    _smtp_waited=$(( _smtp_waited + 5 ))
done
echo ""
if [[ "$_smtp_banner" != 220* ]]; then
    warn "$(printf "$MSG_SMTP_TIMEOUT" "$_smtp_waited" "$PORT_SMTP")"
fi

# ══════════════════════════════════════════════════════════════════════════════
# 18. Проверка лог-файлов qmail
# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_SECTION_LOGS"
info "$MSG_LOGS_CHECKING"

_check_log() {
    local rel="$1"
    local full="$INSTALL_DIR/$rel"
    if [[ ! -e "$full" ]]; then
        warn "$(printf "$MSG_LOG_MISSING" "$rel")"
    else
        local lines
        lines=$(wc -l < "$full" 2>/dev/null || echo 0)
        if [[ "${lines:-0}" -eq 0 ]]; then
            warn "$(printf "$MSG_LOG_EMPTY" "$rel")"
        else
            ok "$(printf "$MSG_LOG_OK" "$rel" "$lines")"
        fi
    fi
}

_check_log "logs/qmail/spamorez/qmail-queue.log"
_check_log "logs/qmail/qmail-send/quaran_clean.log"
_check_log "logs/qmail/qmail-send/quarantine.log"

# grep inside container (bypasses host uid/permission issues, no tai64n needed)
# grep -c exits 1 when no match — pipe through head -1 to avoid || echo 0 doubling
_qmail_grep() {
    docker compose -f docker-compose.yml exec -T qmail \
        grep -c "$2" "$1" 2>/dev/null | head -1
}

# ── qmail-send/current ───────────────────────────────────────────────────────
echo ""
info "$MSG_QMAILSEND_CHECKING"
_check_log "logs/qmail/qmail-send/current"

_n=$(_qmail_grep /var/log/qmail/qmail-send/current "status: local")
if [[ "${_n:-0}" -gt 0 ]]; then
    ok "$MSG_QMAILSEND_RUNNING"
else
    warn "$MSG_QMAILSEND_NO_STATUS"
fi

_n=$(_qmail_grep /var/log/qmail/qmail-send/current "Connecting database:")
if [[ "${_n:-0}" -gt 0 ]]; then
    ok "$MSG_QMAILSEND_DB_OK"
else
    warn "$MSG_QMAILSEND_NO_DB"
fi

_n=$(_qmail_grep /var/log/qmail/qmail-send/current "Unable to init database")
if [[ "${_n:-0}" -gt 0 ]]; then
    warn "$MSG_QMAILSEND_DB_FAIL"
fi

# ── qmail-smtpd/current ──────────────────────────────────────────────────────
echo ""
info "$MSG_QMAILSMTPD_CHECKING"
_check_log "logs/qmail/qmail-smtpd/current"

_n=$(_qmail_grep /var/log/qmail/qmail-smtpd/current "spz-smtp\|SMTP Session")
if [[ "${_n:-0}" -gt 0 ]]; then
    ok "$(printf "$MSG_QMAILSMTPD_SESSIONS" "$_n")"
else
    warn "$MSG_QMAILSMTPD_EMPTY"
fi

# ══════════════════════════════════════════════════════════════════════════════
header "$MSG_DONE_HEADER"
# ══════════════════════════════════════════════════════════════════════════════

docker compose -f docker-compose.yml ps

echo ""
echo -e "${GREEN}${BOLD}═══════════════════════════════════════════════════${NC}"
echo -e "${GREEN}${BOLD}  $(printf "$MSG_DONE_TITLE" "$SPZ_VERSION")${NC}"
echo -e "${GREEN}${BOLD}═══════════════════════════════════════════════════${NC}"
echo ""
_IP=$(hostname -I | awk '{print $1}')
echo -e "  ${BOLD}SMTP:${NC}        ${_IP}:${PORT_SMTP}"
echo -e "  ${BOLD}HTTP:${NC}        http://${_IP}:${PORT_HTTP}/"
echo -e "  ${BOLD}HTTPS:${NC}       https://${_IP}:${PORT_HTTPS}/"
echo -e "  ${BOLD}MySQL:${NC}       127.0.0.1:${PORT_MYSQL}"
echo ""
echo -e "${BOLD}$MSG_MYSQL_PASS_LABEL${NC}"
echo -e "    sudo cat $INSTALL_DIR/data/credentials/.my.cnf"
echo ""
echo -e "${BOLD}$MSG_MANAGEMENT_LABEL${NC}"
echo -e "    cd $INSTALL_DIR"
echo -e "    docker compose ps"
echo -e "    docker compose logs -f --tail=100"
echo ""
echo -e "${BOLD}$MSG_LICENSE_LABEL${NC}"
echo -e "$MSG_LICENSE_URL"
echo ""
