"""Pipeline ML automatise : split -> entrainement (--register) -> promotion.

Orchestre en une seule commande (`python pipeline.py`) les etapes du fil rouge
menant a la mise a disposition du modele dans le MLflow Model Registry :

  1. split    : (re)cree data/{train,validation,test}.parquet depuis /data/modelling
                (strategie de split figee dans lab/constants.py) ;
  2. train    : entraine la strategie de service (`full`) et enregistre une
                nouvelle version du modele dans le Registry (log_model + --register) ;
  3. promote  : (re)pointe l'alias `champion` vers cette nouvelle version.

Chaque etape est un sous-processus lance avec `check=True` : si une etape echoue,
le pipeline s'arrete immediatement avec un code de sortie non nul et n'execute
aucune etape suivante (Partie 2, Q2.2). Le pipeline est reexecutable sans effet
de bord indesirable (idempotence, Q2.3) : le split est deterministe, `--register`
empile proprement une nouvelle version, et l'alias `champion` est repointe
(jamais duplique).

Les etapes reutilisent telles quelles les CLI du package `lab/` (memes commandes
qu'une execution manuelle des TP precedents) : le pipeline ne fait que les
enchainer et calculer le numero de la version a promouvoir.
"""

import logging
import subprocess
import sys

from mlflow import MlflowClient

from lab import constants

logging.basicConfig(level=logging.INFO, format="%(asctime)s [pipeline] %(message)s")
logger = logging.getLogger("pipeline")


def run_step(title: str, module_args: list[str]) -> None:
    """Execute une etape (module `lab`) dans un sous-processus ; stoppe si elle echoue."""
    logger.info("=== ETAPE %s : python -m %s", title, " ".join(module_args))
    subprocess.run([sys.executable, "-m", *module_args], check=True)


def latest_model_version(name: str) -> int:
    """Numero de la derniere version enregistree du modele (celle qui vient d'etre creee)."""
    client = MlflowClient()
    versions = client.search_model_versions(f"name = '{name}'")
    if not versions:
        raise RuntimeError(f"Aucune version enregistree pour le modele '{name}'.")
    return max(int(mv.version) for mv in versions)


def main() -> None:
    strategy = constants.SERVING_STRATEGY
    name = constants.REGISTERED_MODEL_NAME
    alias = constants.MODEL_ALIAS

    # 1. Preparation des jeux de donnees (deterministe).
    run_step("split", ["lab.split.cli"])

    # 2. Entrainement + enregistrement d'une nouvelle version dans le Model Registry.
    run_step("train", ["lab.modeling.cli", strategy.value, "--register"])

    # 3. Promotion : repointer l'alias vers la version qui vient d'etre enregistree.
    version = latest_model_version(name)
    run_step("promote", ["lab.registry.cli", "promote", "--version", str(version), "--alias", alias])

    logger.info("Pipeline termine avec succes : %s v%s -> alias '%s'", name, version, alias)


if __name__ == "__main__":
    main()
