"""Tests du pont moteur G3-A : kwargs corrects, managers lazy, engine injecté.

Aucun vrai manager, aucun modèle, aucun réseau : tout est injecté via factories.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from gui_v6.engine_bridge import (
    EngineSettings,
    ManagerState,
    NerManagers,
    build_engine_kwargs,
    make_process_fn,
)


class FakeManager:
    def __init__(self, name):
        self.name = name


def _counting_factories(counter, fail_camembert=False):
    def camembert():
        counter["camembert"] += 1
        if fail_camembert:
            raise RuntimeError("modèle absent")
        return FakeManager("camembert")

    def eds():
        counter["eds"] += 1
        return FakeManager("eds")

    def gliner():
        counter["gliner"] += 1
        return FakeManager("gliner")

    return {"camembert": camembert, "eds": eds, "gliner": gliner}


# -- kwargs ----------------------------------------------------------------

def test_kwargs_defaults_v5_like():
    settings = EngineSettings(config_path=Path("/tmp/cfg.yml"), ogc_label="OCG")
    kwargs = build_engine_kwargs(settings, managers=None)
    assert kwargs["make_vector_redaction"] is False
    assert kwargs["also_make_raster_burn"] is True
    assert kwargs["config_path"] == Path("/tmp/cfg.yml")
    assert kwargs["ogc_label"] == "OCG"
    # Sans managers : pas de NER.
    assert kwargs["use_hf"] is False


def test_kwargs_with_loaded_managers():
    settings = EngineSettings(enable_eds=True, enable_gliner=True)
    counter = {"camembert": 0, "eds": 0, "gliner": 0}
    managers = NerManagers(
        settings,
        factories=_counting_factories(counter),
        caps_provider=_caps_provider(eds_ok=True, gliner_ok=True),
    )
    managers.ensure_loaded()
    kwargs = build_engine_kwargs(settings, managers)
    assert kwargs["use_hf"] is True
    assert kwargs["camembert_manager"].name == "camembert"
    assert kwargs["ner_manager"].name == "eds"
    assert kwargs["gliner_manager"].name == "gliner"


def test_kwargs_ner_disabled():
    settings = EngineSettings(use_local_ner=False)
    counter = {"camembert": 0, "eds": 0, "gliner": 0}
    managers = NerManagers(settings, factories=_counting_factories(counter))
    managers.ensure_loaded()
    kwargs = build_engine_kwargs(settings, managers)
    assert kwargs["use_hf"] is False
    assert counter["camembert"] == 0  # NER désactivé : rien chargé


# -- lazy loading ----------------------------------------------------------

def test_managers_not_loaded_on_init():
    settings = EngineSettings()
    counter = {"camembert": 0, "eds": 0, "gliner": 0}
    NerManagers(settings, factories=_counting_factories(counter))
    # Aucune factory appelée à la construction.
    assert counter == {"camembert": 0, "eds": 0, "gliner": 0}


def test_managers_load_once_and_state():
    settings = EngineSettings(enable_eds=True)
    counter = {"camembert": 0, "eds": 0, "gliner": 0}
    managers = NerManagers(
        settings,
        factories=_counting_factories(counter),
        caps_provider=_caps_provider(eds_ok=True, gliner_ok=True),
    )
    assert managers.state == ManagerState.NOT_LOADED
    assert managers.ensure_loaded() == ManagerState.READY
    assert managers.ensure_loaded() == ManagerState.READY  # idempotent
    assert counter["camembert"] == 1  # chargé une seule fois
    assert counter["eds"] == 1
    assert counter["gliner"] == 0  # non activé


def test_managers_unavailable_when_camembert_fails():
    settings = EngineSettings()
    counter = {"camembert": 0, "eds": 0, "gliner": 0}
    managers = NerManagers(settings, factories=_counting_factories(counter, fail_camembert=True))
    assert managers.ensure_loaded() == ManagerState.UNAVAILABLE
    assert managers.use_hf is False


def test_optional_manager_failure_is_tolerated():
    settings = EngineSettings(enable_gliner=True)

    def factories():
        def camembert():
            return FakeManager("camembert")

        def gliner():
            raise RuntimeError("gliner ko")

        def eds():
            return FakeManager("eds")

        return {"camembert": camembert, "eds": eds, "gliner": gliner}

    managers = NerManagers(
        settings,
        factories=factories(),
        caps_provider=_caps_provider(eds_ok=True, gliner_ok=True),
    )
    assert managers.ensure_loaded() == ManagerState.READY  # gliner ko ne bloque pas
    assert managers.use_hf is True


# -- garde-fou capabilities runtime ----------------------------------------


class _FakeCap:
    """Capability minimale pour injecter une sonde dans les tests."""

    def __init__(self, available, reason="(test)"):
        self.available = available
        self.reason = reason


def _caps_provider(eds_ok, gliner_ok):
    def provider():
        return {
            "camembert": _FakeCap(True),
            "eds": _FakeCap(eds_ok),
            "gliner": _FakeCap(gliner_ok),
        }

    return provider


def test_guard_disables_unavailable_eds_before_load():
    # Profil/config forçant EDS alors que le moteur n'est pas embarqué.
    settings = EngineSettings(enable_eds=True)
    counter = {"camembert": 0, "eds": 0, "gliner": 0}
    managers = NerManagers(
        settings,
        factories=_counting_factories(counter),
        caps_provider=_caps_provider(eds_ok=False, gliner_ok=True),
    )
    assert managers.ensure_loaded() == ManagerState.READY
    assert settings.enable_eds is False  # désactivation forcée
    assert counter["eds"] == 0  # jamais tenté de charger
    assert managers.as_kwargs()["ner_manager"] is None


def test_guard_disables_unavailable_gliner_before_load():
    settings = EngineSettings(enable_gliner=True)
    counter = {"camembert": 0, "eds": 0, "gliner": 0}
    managers = NerManagers(
        settings,
        factories=_counting_factories(counter),
        caps_provider=_caps_provider(eds_ok=True, gliner_ok=False),
    )
    assert managers.ensure_loaded() == ManagerState.READY
    assert settings.enable_gliner is False
    assert counter["gliner"] == 0
    assert managers.as_kwargs()["gliner_manager"] is None


def test_guard_keeps_available_engine_enabled():
    settings = EngineSettings(enable_eds=True, enable_gliner=True)
    counter = {"camembert": 0, "eds": 0, "gliner": 0}
    managers = NerManagers(
        settings,
        factories=_counting_factories(counter),
        caps_provider=_caps_provider(eds_ok=True, gliner_ok=True),
    )
    assert managers.ensure_loaded() == ManagerState.READY
    assert settings.enable_eds is True
    assert settings.enable_gliner is True
    assert counter["eds"] == 1
    assert counter["gliner"] == 1


def test_guard_failsafe_when_probe_raises():
    settings = EngineSettings(enable_eds=True)
    counter = {"camembert": 0, "eds": 0, "gliner": 0}

    def boom():
        raise RuntimeError("probe ko")

    managers = NerManagers(
        settings, factories=_counting_factories(counter), caps_provider=boom
    )
    # Best-effort : une sonde en échec ne bloque pas le chargement et ne
    # modifie pas les réglages (les try/except de load protègent déjà).
    assert managers.ensure_loaded() == ManagerState.READY
    assert settings.enable_eds is True
    assert counter["eds"] == 1


# -- make_process_fn -------------------------------------------------------

def test_process_fn_calls_engine_with_kwargs(tmp_path):
    settings = EngineSettings()
    counter = {"camembert": 0, "eds": 0, "gliner": 0}
    managers = NerManagers(settings, factories=_counting_factories(counter))
    captured = {}

    def fake_engine(doc_path, out_dir, **kwargs):
        captured["doc"] = doc_path
        captured["out"] = out_dir
        captured["kwargs"] = kwargs
        return {"status": "ok"}

    fn = make_process_fn(settings, managers=managers, engine=fake_engine)
    # Avant tout traitement, aucun manager chargé.
    assert counter["camembert"] == 0

    result = fn(tmp_path / "doc.pdf", tmp_path / "out")

    assert result == {"status": "ok"}
    assert captured["doc"] == tmp_path / "doc.pdf"
    assert captured["kwargs"]["make_vector_redaction"] is False
    assert captured["kwargs"]["also_make_raster_burn"] is True
    assert captured["kwargs"]["use_hf"] is True
    assert captured["kwargs"]["camembert_manager"].name == "camembert"
    # Le chargement n'a eu lieu qu'à l'appel de process_fn.
    assert counter["camembert"] == 1
