# Multi-stage, mirroring catalogue_frontend/Dockerfile: a build stage that
# resolves dependencies, then a clean runtime stage that copies in only the
# result. There it is `npm ci` -> dist/; here it is pip -> a virtualenv.

# ---- Build stage ----
FROM python:3.11-slim AS build
WORKDIR /app

# Dependencies land in a self-contained venv so the runtime stage can take that
# one directory and leave pip, its HTTP cache and the downloaded wheels behind.
#
# requirements.txt is copied on its own, ahead of the source, for the same
# reason the frontend stage copies package.json before the rest of the app:
# this layer is cached on the file's checksum, so editing a router does not
# reinstall torch.
#
# psycopg[binary] avoids needing libpq-dev; sentence-transformers/scikit-learn/
# scipy all ship prebuilt wheels for this image, so no compiler is needed and
# this stage installs no build toolchain. Playwright's Python package installs,
# but its browser binary is NOT installed here - it's only a last-resort
# image-search fallback (see requirements.txt); run `playwright install
# chromium` in the container if you need that specific fallback tier.
COPY requirements.txt .
RUN python -m venv /opt/venv \
    && /opt/venv/bin/pip install --no-cache-dir -r requirements.txt


# ---- Runtime stage ----
FROM python:3.11-slim AS runtime
WORKDIR /app

# PATH: putting the venv first is what makes a bare `python`/`uvicorn` resolve
#   to it - there is no "activate" step in a container.
# PYTHONUNBUFFERED: without it Dokploy's log view stays empty until a buffer
#   happens to fill, so startup errors surface minutes after the container died.
# PYTHONDONTWRITEBYTECODE: no .pyc to write into a read-only-ish image layer.
ENV PATH="/opt/venv/bin:$PATH"
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

COPY --from=build /opt/venv /opt/venv

COPY app ./app
COPY cli ./cli
COPY scripts ./scripts
COPY data ./data

# Pristine copies of everything the app also WRITES to, kept at a path that is
# never mounted over.
#
# /app/data/seed_catalogs and /app/app/intelligence/artifacts both need volumes
# (products added through the UI are appended to the first, retrained models
# are written to the second - otherwise a redeploy throws both away). But
# mounting a volume there hides the copies shipped in this image: a *named*
# volume is seeded from the image on first use, a *bind* mount is not, and
# Dokploy offers both. A bind mount would leave the API with zero seed catalogs
# and zero trained models, with nothing in the logs saying why.
#
# So keep a second copy here. On startup restore_bundled_assets()
# (app/infrastructure/persistence.py) copies in whatever the mounted directory
# is missing, and never overwrites what is already there.
RUN mkdir -p /app/.bundled \
    && cp -a /app/data/seed_catalogs /app/.bundled/seed_catalogs \
    && cp -a /app/app/intelligence/artifacts /app/.bundled/artifacts

# Declared so `docker run` without an explicit -v still gets an anonymous
# volume rather than writing into the container layer. Dokploy (and the compose
# file) name them properly; this is the floor, not the recommended setup.
VOLUME ["/app/data", "/app/app/intelligence/artifacts"]

# The port uvicorn binds. Overridable because Dokploy assigns the container
# port per service - the frontend image answers on both 80 and 3000 for the
# same reason. A single process cannot listen twice, so this is the knob:
#   PORT=3000  in the service's environment, if you standardise on 3000.
ENV PORT=8000
EXPOSE 8000

# Liveness only. /api/health always answers 200 - it reports Postgres and
# Ollama in the body as "degraded" rather than failing - which is deliberate
# here: a healthcheck that went red whenever Postgres blinked would have
# Dokploy restart a perfectly healthy API in a loop.
#
# The 10s timeout is not padding: the handler probes Ollama over HTTP with a
# 3s timeout of its own, so an unreachable Ollama makes every check take ~3s.
# start-period covers first boot, where the venv is still cold.
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD python -c "import os,urllib.request;urllib.request.urlopen('http://127.0.0.1:'+os.environ.get('PORT','8000')+'/api/health',timeout=8)" || exit 1

# `exec` matters: without it the shell stays PID 1 and Docker's SIGTERM never
# reaches uvicorn, so every deploy waits out the 10s kill timeout instead of
# shutting down cleanly. sh is only here to expand $PORT.
#
# --proxy-headers/--forwarded-allow-ips: this container is never reached
# directly - nginx (and, in prod, Dokploy's Traefik) sit in front of it.
# Without these, uvicorn ignores X-Forwarded-Proto and reports every request as
# plain http, so any redirect or generated absolute URL would downgrade an
# https request.
CMD ["sh", "-c", "exec uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000} --proxy-headers --forwarded-allow-ips '*'"]
