# syntax=docker/dockerfile:1
#
# Targets:
#   prod (default, last stage)  gunicorn image with assets and static files baked in
#   dev                          dependencies only; the source is bind-mounted by compose.override.yml
#
# Stages are ordered so that editing application code only invalidates the cheap final layers:
# apt packages, Python dependencies and node modules are all cached separately.

# ---- Frontend assets (multi-arch: official node image) ----
FROM node:26-slim AS assets
WORKDIR /app

COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci

COPY gulpfile.js ./
COPY pipeline/source_assets ./pipeline/source_assets
RUN npm run build


# ---- Shared Python base: runtime system libraries only ----
FROM python:3.14-slim-trixie AS python-base

# Keep downloaded packages between builds (the default Docker config for Debian images deletes them)
RUN rm -f /etc/apt/apt.conf.d/docker-clean \
    && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    apt-get update \
    && apt-get install -y --no-install-recommends libcairo2

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    UV_PROJECT_ENVIRONMENT=/opt/venv \
    UV_LINK_MODE=copy \
    PATH="/opt/venv/bin:$PATH"


# ---- Python dependencies (only rebuilt when pyproject.toml / uv.lock change) ----
FROM python-base AS deps-base
COPY --from=ghcr.io/astral-sh/uv:0.12.21 /uv /uvx /bin/

# pycairo (via z3c.rml) has no wheels and is compiled against cairo
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    apt-get update \
    && apt-get install -y --no-install-recommends build-essential pkg-config libcairo2-dev

WORKDIR /app

FROM deps-base AS deps-prod
ENV UV_COMPILE_BYTECODE=1
RUN --mount=type=cache,target=/root/.cache/uv \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    uv sync --frozen --no-install-project --no-dev

FROM deps-base AS deps-dev
RUN --mount=type=cache,target=/root/.cache/uv \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    uv sync --frozen --no-install-project


# ---- Development: dependencies only, source and static assets are mounted at runtime ----
FROM python-base AS dev
COPY --from=deps-dev /opt/venv /opt/venv
WORKDIR /app
EXPOSE 8000
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]


# ---- Static files: depends on the source, so this is the first stage that reruns on a code change ----
FROM python-base AS static
COPY --from=deps-prod /opt/venv /opt/venv
WORKDIR /app
COPY . /app
COPY --from=assets /app/pipeline/built_assets /app/pipeline/built_assets
# Placeholder values only satisfy settings that are mandatory when DEBUG is off; they are not kept in the image
RUN DEBUG=false EMAIL_HOST=build EMAIL_HOST_USER=build EMAIL_HOST_PASSWORD=build EMAIL_FROM=build@example.com \
    python manage.py collectstatic --noinput


# ---- Production image ----
FROM python-base AS prod
RUN addgroup --system app \
    && adduser --system --group --home /home/app app

COPY --from=deps-prod /opt/venv /opt/venv
WORKDIR /app
COPY --chown=app:app . /app
COPY --from=static --chown=app:app /app/static /app/static
COPY --from=assets --chown=app:app /app/pipeline/built_assets /app/pipeline/built_assets

# DEBUG must never be on in a built image; enable it explicitly via the environment if needed
ENV DEBUG=false

# Declared last so a new commit SHA does not invalidate any of the layers above
ARG GIT_SHA=unknown
ENV GIT_SHA=$GIT_SHA

USER app
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "PyRIGS.wsgi"]
