# syntax=docker/dockerfile:1
#
# Trois étapes (ADR-0045, action n°1) :
#   dev        — reproduit exactement l'image d'avant cette action : code
#                monté par `docker-compose.yml`, dépendances `[dev]` en
#                éditable, `--reload`. Ciblée explicitement
#                (`build: { target: dev }`), jamais l'étape par défaut.
#   builder    — construit les roues de l'application et de ses dépendances,
#                sans `[dev]`. N'existe que pour nourrir l'étape suivante :
#                aucune image ne la cible.
#   production — **dernière étape, donc cible par défaut d'un
#                `docker build .` sans `--target`.** Roues seules, aucune
#                source copiée, utilisateur non root, `HEALTHCHECK`.
#
# python:3.12-alpine, épinglé par digest pour la reproductibilité des builds
# (cf. docs/adr/0007-image-base-python-api-alpine.md).
FROM python:3.12-alpine@sha256:4c47124a8391cb7a9f571164147d154777cf012a4ece5f86097130d7a4478111 AS dev

WORKDIR /app

# COPY . . précède le pip install : `-e .` construit un editable install qui
# a besoin du paquet `app/` présent sur disque au moment de l'install (sans
# quoi hatchling ne l'enregistre nulle part et `import app` échoue partout
# hors du répertoire de travail — y compris pour `pytest` exécuté seul dans
# l'image). Coût assumé : le layer pip install n'est plus mis en cache
# indépendamment du code source.
# Dépendances depuis le fichier verrouillé, empreintes vérifiées (audit du
# 26/09/2026, EXP-2) : les mêmes versions que la CI. Le paquet lui-même
# ensuite, sans rien résoudre de plus (`--no-deps`).
COPY . .
RUN pip install --no-cache-dir --require-hashes -r requirements-dev.txt \
    && pip install --no-cache-dir --no-deps -e .

EXPOSE 8000

# `--proxy-headers` : uvicorn corrige l'adresse du client depuis
# `X-Forwarded-For`, **uniquement** pour les pairs listés dans
# `FORWARDED_ALLOW_IPS` (127.0.0.1 par défaut). C'est l'adresse que compte la
# limite de débit (ADR-0031) : derrière un proxy non déclaré, tous les
# visiteurs partageraient la sienne.
# `--no-access-log` : le journal d'accès d'uvicorn écrit l'IP du client et
# l'URL complète, chaîne de requête comprise. La ligne `requete` émise par
# `app/core/request_log.py` le remplace, sans ces deux données (constat P1
# n°6 de l'audit du 10/09).
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers", "--no-access-log"]


# ---------------------------------------------------------------------------
# builder — roues de l'application et de ses dépendances, sans [dev]
# ---------------------------------------------------------------------------
FROM python:3.12-alpine@sha256:4c47124a8391cb7a9f571164147d154777cf012a4ece5f86097130d7a4478111 AS builder

WORKDIR /src
COPY . .

# Sans paquets de compilation : asyncpg, uvloop, httptools, websockets,
# watchfiles (tirés par `uvicorn[standard]`), `cryptography` (tiré par
# `PyJWT[crypto]`, ADR-0040) et `pypdf` publient tous des roues musllinux
# prêtes à l'emploi pour Python 3.12 — vérifié par
# `.github/scripts/verifier-images.sh`, qui construit réellement cette étape.
# Si une dépendance future n'en publie plus, l'échec de cette étape le dira
# avant qu'aucune image ne s'en aperçoive en production : ajoutez alors
# `build-base` (et l'en-tête de la bibliothèque manquante) **ici**, jamais
# dans l'étape `production`.
#
# Versions verrouillées (audit du 26/09/2026, EXP-2) : les dépendances
# viennent de `requirements.txt`, chaque roue vérifiée contre son empreinte
# SHA-256 (`--require-hashes`) — deux constructions du même commit donnent
# les mêmes versions, celles que la CI a testées. La roue de l'application
# est construite à part, `--no-deps` : elle ne peut rien tirer d'autre.
RUN pip wheel --no-cache-dir --wheel-dir /wheels --require-hashes -r requirements.txt \
    && pip wheel --no-cache-dir --wheel-dir /wheels --no-deps .


# ---------------------------------------------------------------------------
# production — dernière étape, cible par défaut. Roues seules, non root.
# ---------------------------------------------------------------------------
FROM python:3.12-alpine@sha256:4c47124a8391cb7a9f571164147d154777cf012a4ece5f86097130d7a4478111 AS production

ARG TEUTHIS_VERSION=0.0.0
LABEL org.opencontainers.image.title="Teuthis-CMS API" \
      org.opencontainers.image.description="CMS headless pour collectivités — API Core (FastAPI)" \
      org.opencontainers.image.source="https://github.com/Bastien-OC20/calamars" \
      org.opencontainers.image.licenses="Apache-2.0" \
      org.opencontainers.image.version="${TEUTHIS_VERSION}"

# Utilisateur dédié, uid/gid fixes : une pile de production qui monte un
# volume nommé sur /data doit pouvoir en poser la propriété sans deviner un
# identifiant attribué par le système au moment du build.
RUN addgroup -g 10001 teuthis && adduser -D -u 10001 -G teuthis -h /home/teuthis teuthis

# `/data` : fichiers liés à une personne (ADR-0017) et médiathèque
# (ADR-0041), sur deux répertoires distincts (cf. `app/core/config.py`) —
# **pas d'instruction `VOLUME`** : c'est la pile de production (à venir,
# ADR-0045) qui déclare le volume nommé, pas l'image, pour rester utilisable
# aussi sans volume (essais, CI).
ENV MEDIA_ROOT=/data/media \
    PERSONAL_FILE_ROOT=/data/personal-files \
    PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1
RUN mkdir -p /data/media /data/personal-files && chown -R teuthis:teuthis /data

COPY --from=builder /wheels /wheels
# Roues seules : aucune source de `app/` n'est copiée dans cette étape, donc
# aucun `[dev]` (pytest, ruff, flake8 — vérifié par
# `.github/scripts/verifier-images.sh`) n'a de source à s'y installer même
# par erreur.
# `--no-index` : rien d'autre que ces roues, déjà vérifiées à l'étape
# précédente — l'installation ne contacte pas PyPI.
RUN pip install --no-cache-dir --no-index --no-deps /wheels/*.whl && rm -rf /wheels

COPY --chmod=755 docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh

USER teuthis
WORKDIR /home/teuthis

EXPOSE 8000

# Vise le rôle `api` de `docker-entrypoint.sh` (CMD par défaut de cette
# image) : un conteneur lancé avec le rôle `scheduler` n'écoute aucun port et
# devra **désactiver** ce `HEALTHCHECK` dans la pile de production (à venir),
# ou fournir la sienne. En Python (`urllib`, bibliothèque standard) : Alpine
# n'installe ni `curl` ni `wget` avec les en-têtes attendus par défaut, et
# ajouter l'un des deux pour ce seul usage aurait élargi la surface de
# l'image sans nécessité.
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
    CMD ["python", "-c", "import urllib.request as u; u.urlopen('http://127.0.0.1:8000/health', timeout=3)"]

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["api"]
