"""Contract test: every registered adapter exposes an identical `.predict`.

This is the core v0.1 promise. acaua's tagline is "one API to learn, one to
remember" — if one adapter takes `batch_size` but another takes `bs`, the
promise is broken. The contract test catches that at CI time, not at user
time.

Written red-first (before the adapters exist). In Lane D the test fails at
fixture collection because the adapter modules aren't importable yet; Lane E
makes it green by implementing the two adapters.
"""

from __future__ import annotations

import importlib
import inspect

import pytest

from acaua._data.adapters import ADAPTERS
from acaua.model import Model


@pytest.fixture(scope="module")
def adapter_classes() -> list[type[Model]]:
    """Import every adapter the registry claims to have."""
    classes: list[type[Model]] = []
    for task, (module_path, class_name) in ADAPTERS.items():
        try:
            module = importlib.import_module(module_path)
        except ImportError as e:
            pytest.fail(f"adapter for task {task!r} not importable ({module_path!r}): {e}")
        cls = getattr(module, class_name, None)
        if cls is None:
            pytest.fail(f"adapter module {module_path!r} missing class {class_name!r}")
        assert issubclass(cls, Model), (
            f"{cls!r} is registered for task {task!r} but is not a subclass of acaua.Model"
        )
        classes.append(cls)
    assert len(classes) >= 2, (
        "v0.1 success criterion requires at least 2 adapters "
        "(detection + segmentation). The parity test is not meaningful "
        "with fewer."
    )
    return classes


def test_predict_signature_identical_across_adapters(
    adapter_classes: list[type[Model]],
) -> None:
    """Every adapter's `.predict` signature must match the abstract base."""
    base_sig = inspect.signature(Model.predict)
    for cls in adapter_classes:
        cls_sig = inspect.signature(cls.predict)
        assert cls_sig == base_sig, (
            f"{cls.__name__}.predict signature {cls_sig} != base Model.predict signature {base_sig}"
        )


def test_predict_return_annotation_preserved(
    adapter_classes: list[type[Model]],
) -> None:
    """Return annotation must be preserved.

    Already covered by signature equality, but asserted explicitly so that a
    future adapter that drops the annotation (via a decorator, say) fails
    with a message naming the cause.
    """
    base_ret = inspect.signature(Model.predict).return_annotation
    for cls in adapter_classes:
        cls_ret = inspect.signature(cls.predict).return_annotation
        assert cls_ret == base_ret, (
            f"{cls.__name__}.predict return annotation {cls_ret!r} != base {base_ret!r}"
        )
