# DXA Quality Assessment — контейнер для инференса (GPU / CUDA)
#
# Сборка:  docker build -f Dockerfile_cuda.txt -t dxa-quality:cuda .
# Запуск:  docker run --gpus all -p 8000:8000 dxa-quality:cuda
#          или: docker compose --profile cuda up -d dxa-cuda
#
# База — тот же python:3.11-slim, что и у CPU-варианта, а CUDA и cuDNN приходят
# внутри колёс torch с индексом cu126. Отдельный nvidia/cuda-образ не нужен:
# контейнеру достаточно драйвера хоста, который подставляет
# nvidia-container-toolkit по флагу --gpus all (или секции deploy в compose).
# Требуется драйвер NVIDIA >= 525; для H200 (sm_90) в кластере он заведомо новее.
#
# Индекс cu118 в прежней версии файла не работал: torch 2.8.0 под CUDA 11.8 не
# публикуется (последняя версия там — 2.7.1), поэтому сборка падала на pip.
# Для 2.8.0 доступны только cu126 и cu128; выбран cu126 как наиболее
# распространённый. Под H200 нужен torch>=2.5, sm_90 поддерживается.
#
# Чекпоинт лежит ВНУТРИ образа (см. Dockerfile): монтировать пути не нужно.
FROM python:3.11-slim

# libgl1/libglib2.0-0 нужны opencv (импортируется через зависимости проекта),
# libgomp1 — для параллельных циклов torch. Список совпадает с CPU-образом.
RUN apt-get update && apt-get install -y --no-install-recommends \
    libgl1 \
    libglib2.0-0 \
    libgomp1 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# --- PyTorch под CUDA 12.6 ---
# CUDA-колёса ставим первыми и с явным индексом: иначе pip взял бы с PyPI сборку
# под CUDA 12.6/12.8 произвольной ревизии. torchvision 0.23.0 — парная к torch
# 2.8.0 версия (другие комбинации несовместимы).
RUN pip install --no-cache-dir --upgrade pip \
    && pip install --no-cache-dir \
        --index-url https://download.pytorch.org/whl/cu126 \
        torch==2.8.0 torchvision==0.23.0

# --- Остальные зависимости ---
# Отдельным слоем и после torch: список версий один на оба образа
# (requirements.txt), а уже установленный torch==2.8.0+cu126 pip повторно не
# поставит — требование torch==2.8.0 считается выполненным, потому что локальная
# метка сборки при сравнении версий не учитывается.
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# --- Код ---
COPY src ./src

# Скрипт пакетной обработки (оценка берёт его из корня образа).
COPY run.sh ./run.sh
RUN chmod +x ./run.sh

# Smoke-проверка импорта: падает на сборке, если расходятся зависимости или
# модуль не найден, вместо тихой 500-й на каждом запросе в рантайме.
RUN python -c "import src.main; print('app import ok')"

# --- Проверка статики для офлайн-работы ---
# Веб-интерфейс не должен зависеть от CDN: Tailwind и FontAwesome лежат в
# src/api/static/vendor и src/api/static/webfonts (см. index.html).
RUN python -c "import os; \
    files=['src/api/static/vendor/tailwind.js','src/api/static/vendor/fontawesome.css', \
           'src/api/static/webfonts/fa-solid-900.woff2']; \
    missing=[f for f in files if not os.path.exists(f)]; \
    assert not missing, f'missing frontend assets: {missing}'; \
    print('frontend assets ok')"

# --- Чекпоинт внутри образа ---
# Условие приёмки: модель уже в образе, чтобы проверяющему не приходилось
# указывать путь к каталогу с весами. models/dxa_model.pth отслеживается git,
# поэтому копирование работает и в CI.
COPY models/dxa_model.pth /app/models/dxa_model.pth

# Проверка, что чекпоинт читается и содержит всё нужное для инференса, а также
# что сборка torch видит CUDA: без этого проблемы всплыли бы только в рантайме.
RUN python -c "import torch; \
    ckpt=torch.load('/app/models/dxa_model.pth', map_location='cpu', weights_only=False); \
    missing=[k for k in ('model_state_dict','backbone','head','threshold') if k not in ckpt]; \
    assert not missing, f'checkpoint missing keys: {missing}'; \
    print('checkpoint ok:', ckpt['backbone'], ckpt['head'], 'threshold', ckpt['threshold']); \
    print('torch', torch.__version__, 'cuda built:', torch.version.cuda)"

# --- Переменные окружения ---
# Читается кодом только DXA_MODEL_PATH (src/main.py). MODEL_PATH, DATA_PATH,
# ANNOTATION_DIR swarm передаёт по инерции от прежней версии — код их не
# использует, и они ни на что не влияют.
ENV PYTHONUNBUFFERED=1 \
    PYTHONPATH=/app \
    DXA_MODEL_PATH=/app/models/dxa_model.pth \
    MODEL_PATH=/app/models \
    DATA_PATH=/app/data \
    ANNOTATION_DIR=/app/data/annotations

RUN mkdir -p /app/data /app/logs

EXPOSE 8000

# HEALTHCHECK опирается на /api/v1/health, который отдаёт model_loaded.
# На GPU-устройстве старт дольше: даём запас по start-period.
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD python -c "import urllib.request,sys; \
        r=urllib.request.urlopen('http://127.0.0.1:8000/api/v1/health', timeout=5); \
        sys.exit(0 if r.status==200 else 1)"

CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
