"""Le worker lancé **à froid** connaît les types métier (rapport T0, concern n°1).

Le worker est neutre : il ne connaît que le registre, et un type ne s'y déclare qu'à
l'import du module qui le possède. Un process worker qui n'importe pas `app.keywords.pipeline`
démarre donc avec un registre vide et met `failed` — « type de job inconnu » — **tous** les
imports de la file, sans que rien n'ait l'air cassé au démarrage : ni exception, ni log
d'erreur au lancement, juste des imports qui échouent un par un. C'est le pendant exact du
piège `app/db/models.py` pour Alembic, et il ne se voit qu'en production.

Aucun test in-process ne peut l'attraper : la suite importe `app.keywords.pipeline` pour ses
propres besoins, donc le registre y est *toujours* peuplé, même si `worker._run` ne charge
rien. Il faut un **vrai process neuf**, lancé par sa vraie ligne de commande — et lire ce
qu'il annonce.

Le sous-process pointe volontairement vers une base inexistante : `create_async_engine` ne
se connecte pas, la ligne « worker jobs démarré » est écrite **avant** le premier accès à la
file, et l'on n'a donc ni à préparer une base ni à risquer que ce worker chipe un job de
`data_test` à la suite de tests.
"""

import os
import subprocess
import sys
import time

from app.jobs import registry
from app.jobs.loader import load_job_types
from app.keywords import pipeline

STARTUP_LINE = "worker jobs démarré"
TIMEOUT = 60.0


def test_loader_registers_the_import_type() -> None:
    assert pipeline.JOB_TYPE in load_job_types()
    job_type = registry.get(pipeline.JOB_TYPE)
    assert job_type is not None
    assert job_type.handler is pipeline.run_import_csv
    # `on_fail` est ce qui tient `imports.status` en cohérence sur le chemin d'échec (§3.11).
    assert job_type.on_fail is pipeline.mark_import_failed
    # Un import est long et exclusif : la clé n'est relâchée qu'à `done`/`failed` (socle §1).
    assert job_type.dedup_scope == "active"
    assert pipeline.JOB_TYPE not in registry.queue_scoped_names()


def test_loader_registers_the_api_import_type_and_the_scheduler() -> None:
    """Le même piège vaut pour T8 : un worker qui n'importe pas `app.integrations` tournerait
    sans jamais honorer un rendez-vous de synchro — en silence, puisque rien n'échoue."""
    from app.integrations import pipeline as api_pipeline

    names = load_job_types()
    assert api_pipeline.JOB_TYPE in names
    job_type = registry.get(api_pipeline.JOB_TYPE)
    assert job_type is not None
    assert job_type.handler is api_pipeline.run_import_api
    assert job_type.on_fail is pipeline.mark_import_failed
    assert job_type.dedup_scope == "active"
    #  Le planificateur n'est pas un job : c'est une tâche périodique du worker (spec §9).
    assert "import_schedules" in registry.periodic_names()


def test_a_cold_worker_process_announces_the_import_type() -> None:
    environment = {
        **os.environ,
        # Base injoignable **à dessein** : voir l'en-tête du module.
        "DATABASE_URL": "postgresql+asyncpg://nobody:nobody@127.0.0.1:1/nowhere",
        "PYTHONUNBUFFERED": "1",
    }
    #  `with` ferme les tubes et attend le process : sans lui, le descripteur du tube fuit
    #  et `-W error` transforme le `ResourceWarning` en échec.
    with subprocess.Popen(
        [sys.executable, "-m", "app.jobs.worker"],
        stderr=subprocess.PIPE,
        stdout=subprocess.DEVNULL,
        text=True,
        env=environment,
    ) as process:
        assert process.stderr is not None
        try:
            deadline = time.monotonic() + TIMEOUT
            announcement = ""
            while time.monotonic() < deadline:
                line = process.stderr.readline()
                if not line:
                    break
                if STARTUP_LINE in line:
                    announcement = line
                    break
            assert STARTUP_LINE in announcement, "le worker n'a jamais annoncé son démarrage"
            assert pipeline.JOB_TYPE in announcement, (
                f"un worker neuf ignore le type {pipeline.JOB_TYPE!r} : il ferait échouer tous "
                f"les imports en « type de job inconnu ». Annonce : {announcement.strip()!r}"
            )
        finally:
            process.terminate()
