#!/bin/sh
# Идемпотентное скачивание GGUF-модели для локального LLM-сервера
# (llama.cpp), см. ADR-004 (`docs/architecture/adr/004-ai-tier-matrix.md`).
#
# Генерализовано под 3 уровня AI (min/medium/max, разные модели семейства
# Qwen3.5) — конкретную модель задают переменные окружения, которые пишет
# `install.sh` при выборе пресета 3/4/5 (см. `docs/deploy/llm-setup.md`):
#   LLM_MODEL_FILE      — имя файла модели внутри $MODEL_DIR (ПОД ЭТИМ именем
#                         сохраняется локально — не обязано совпадать с
#                         именем файла в источнике, см. GGUF_URL/GGUF_FILE_NAME)
#   LLM_MODEL_URL       — прямая ссылка на GGUF (resolve/main/...)
#   LLM_MODEL_MIN_SIZE  — ожидаемый минимальный размер файла в байтах
#                         (страховка от оборванной/битой докачки)
#   LLM_TOKENIZER_FILE  — имя файла токенизатора внутри $MODEL_DIR
#   LLM_TOKENIZER_URL   — ссылка на tokenizer.json базовой модели (нужен
#                         QwenTokenCounter, backend/core/summarization/tokens.py)
#
# Имена файлов (LLM_MODEL_FILE/LLM_TOKENIZER_FILE) — КОНТРАКТ с
# `backend/services/ai_tiers.py` (см. докстринг модуля): детект доступности
# уровня AI и сам плагин `qwen_local` (`tokenizer_path`/`model` в
# `config/plugins.yaml`) ищут файлы по путям `{QWEN_MODELS_ROOT}/{model}.gguf`
# и `{QWEN_MODELS_ROOT}/{model}.tokenizer.json` из этого модуля — install.sh
# обязан передавать сюда ИМЕННО эти имена, не имена файлов в источнике.
#
# Дефолты ниже соответствуют уровню `min` (Qwen3.5-4B-Instruct, ADR-004) —
# скрипт можно запускать вручную без install.sh (см. `docs/deploy/llm-setup.md`).
#
# Запускается init-контейнером `llm-model-init` (образ curlimages/curl,
# профили compose `llm`/`llm-gpu`) перед стартом сервиса `llm`/`llm-gpu`.
#
# Идемпотентность: перед скачиванием каждого файла проверяется его
# наличие и размер (не меньше ожидаемого минимума) — повторный запуск
# на уже заполненном томе (в т.ч. после смены пресета на модель ДРУГОГО
# уровня, ранее уже скачанную в этот же volume) ничего не перекачивает.
set -eu

MODEL_DIR="${MODEL_DIR:-/models/qwen}"

GGUF_URL="${LLM_MODEL_URL:-https://huggingface.co/unsloth/Qwen3.5-4B-GGUF/resolve/main/Qwen3.5-4B-Q4_K_M.gguf}"
GGUF_FILE_NAME="${LLM_MODEL_FILE:-qwen3.5-4b-instruct-q4_k_m.gguf}"
GGUF_FILE="${MODEL_DIR}/${GGUF_FILE_NAME}"
# Порог занижен относительно реального размера файла — не ловить ложные
# срабатывания на разных ревизиях модели, но отсекать битые/оборванные
# докачки (пустой или сильно урезанный файл). install.sh передаёт более
# точный порог для выбранного уровня.
GGUF_MIN_SIZE="${LLM_MODEL_MIN_SIZE:-2000000000}"

TOKENIZER_URL="${LLM_TOKENIZER_URL:-https://huggingface.co/Qwen/Qwen3.5-4B/resolve/main/tokenizer.json}"
TOKENIZER_FILE_NAME="${LLM_TOKENIZER_FILE:-qwen3.5-4b-instruct.tokenizer.json}"
TOKENIZER_FILE="${MODEL_DIR}/${TOKENIZER_FILE_NAME}"
# tokenizer.json семейства Qwen3.5 занимает ~12 МБ (словарь + merges) —
# порог просто отсекает пустой/HTML-ответ вместо файла.
TOKENIZER_MIN_SIZE=$((1 * 1000 * 1000))

# Размер существующего файла в байтах (0, если файла нет).
file_size() {
    if [ -f "$1" ]; then
        wc -c < "$1" | tr -d ' '
    else
        echo 0
    fi
}

# Скачать файл по URL, если он отсутствует или меньше минимального размера.
download_if_missing() {
    url="$1"
    dest="$2"
    min_size="$3"
    name="$4"

    size=$(file_size "$dest")
    if [ "$size" -ge "$min_size" ]; then
        echo "[download-model] ${name}: уже скачан (${dest}, ${size} байт) — пропуск"
        return 0
    fi

    echo "[download-model] ${name}: скачивание из ${url} в ${dest}"
    tmp="${dest}.part"
    # -f — считать HTTP-ошибки (4xx/5xx) провалом; -L — следовать
    # редиректам HuggingFace на CDN; --retry — устойчивость к обрывам сети.
    curl -fL --retry 5 --retry-delay 5 -o "$tmp" "$url"

    new_size=$(file_size "$tmp")
    if [ "$new_size" -lt "$min_size" ]; then
        echo "[download-model] ${name}: скачанный файл подозрительно мал (${new_size} байт) — прерывание" >&2
        rm -f "$tmp"
        exit 1
    fi

    mv "$tmp" "$dest"
    echo "[download-model] ${name}: готово (${dest}, ${new_size} байт)"
}

mkdir -p "$MODEL_DIR"

download_if_missing "$GGUF_URL" "$GGUF_FILE" "$GGUF_MIN_SIZE" "GGUF-модель (${GGUF_FILE_NAME})"
download_if_missing "$TOKENIZER_URL" "$TOKENIZER_FILE" "$TOKENIZER_MIN_SIZE" "${TOKENIZER_FILE_NAME}"

echo "[download-model] всё готово: ${MODEL_DIR}"
