# Python 3.12 rather than the newest release: asyncpg and SQLAlchemy wheels are reliably
# available here, which matters more than a version number on a project with one deadline.
#
# Four stages, and the split exists for two reasons. Dependencies are resolved once from
# `uv.lock` so every build of a given commit installs byte-identical versions; and the
# development tools (pytest, mypy, ruff) are installed in the `dev` stage only, so the
# production image cannot run them and does not carry their transitive dependencies.
#
# `prod` is last, so it is what `docker build` produces by default. The dev overlay asks
# for `target: dev` explicitly.

FROM python:3.12-slim AS base

# The virtualenv lives OUTSIDE /app on purpose. The dev overlay bind-mounts ./api onto
# /app, which would hide anything the build wrote there — a venv at /app/.venv simply
# disappears at runtime. Keeping it in /opt/venv means source can be mounted freely.
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    UV_COMPILE_BYTECODE=1 \
    UV_LINK_MODE=copy \
    UV_PROJECT_ENVIRONMENT=/opt/venv \
    PATH="/opt/venv/bin:$PATH"

# Pinned, not `:latest`. A resolver that changes under you defeats the point of committing
# a lock file — this is the tool that reads it.
COPY --from=ghcr.io/astral-sh/uv:0.12.5 /uv /uvx /bin/

WORKDIR /app

# Dependency layer — cached until pyproject/uv.lock change. `--frozen` means "install
# exactly what the lock says and fail if it is missing or stale". There is deliberately no
# fallback: the previous `|| uv sync` arm silently resolved fresh versions whenever the
# lock was absent, which is how an unpinned build hides in plain sight.
COPY pyproject.toml uv.lock ./

# ---------------------------------------------------------------- runtime dependencies
FROM base AS deps-prod
RUN uv sync --frozen --no-install-project --no-dev

# ------------------------------------------------- runtime dependencies + dev toolchain
FROM base AS deps-dev
RUN uv sync --frozen --no-install-project

# ------------------------------------------------------------------------- dev image
# Source is bind-mounted over /app by the dev overlay, so what is copied here only matters
# for one-shot `docker compose run` commands. Stays root: `make migration` writes a new
# file into the mounted alembic/versions, and a uid mismatch there is a confusing failure
# in exchange for nothing — nothing in development is exposed.
FROM deps-dev AS dev
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

# ------------------------------------------------------------------------ prod image
FROM deps-prod AS prod

# Non-root. The uid is fixed at 1001 rather than left to the system because /srv/media is
# a bind mount from the host (design D4) — the API writes uploaded card artwork there, and
# the host directory must be owned by this same uid or every upload fails with EACCES.
# `make prod` chowns it; this is the number it uses.
RUN groupadd --system --gid 1001 app \
    && useradd --system --uid 1001 --gid app --no-create-home app \
    && mkdir -p /srv/media \
    && chown -R app:app /srv/media

COPY --chown=app:app . .

USER app

EXPOSE 8000

# Overridden per service by the compose files (the worker runs the same image with a
# different command).
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
